/*
*
==============================================================================
*  Name        : 
*  Part of     : Omen Game Engine - Example Game
*  Description : 
*  Version     : 1.0
*
*  Copyright (c) 2013 Lauri Kortevaara.
* 
==============================================================================
*/

#include "OmenGame.h"
#include <iostream>
#include <OmenMath.h>

using namespace Omen;

/// Static debug functions
static void listGraphicsDrivers( Omen::IGraphicsServer* graphicsServer);

static bool bAnimate = false;

/// <summary></summary>
/// <param name=""></param>
OmenGame::OmenGame(void)
    : m_pKernel(GetOmenFactory().getKernel()),
      m_pSceneManager(NULL),
      m_pCurrentCamera(NULL),
      m_pTorusModel(NULL),
      m_pCube(NULL),
      m_playerSpeedForward(0.0f),
      m_yaw(0.0),
      m_pitch(0.0),
      m_roll(0.0),
      m_keyUp(false),
      m_keyDown(false),
      m_keyLeft(false),
      m_keyRight(false),
      m_keyQ(false), 
      m_keyE(false)
{
}

/// <summary></summary>
/// <param name=""></param>
OmenGame::~OmenGame(void)
{
}

/// <summary></summary>
/// <param name=""></param>
bool OmenGame::initialize()
{
    if( !m_pKernel.initialize(L"../../../data/config.cfg") )
        throw std::runtime_error(  "Unable to initialize the engine!" );

    m_pKernel.setProperty( L"octree_depth", L"1" );

    // Get scene manager
    m_pSceneManager = m_pKernel.getSceneManager();

    if( !m_pSceneManager )
        throw std::runtime_error( "Unable to get Omen SceneManager!" );

    // Get asset manager
    m_pAssetManager = m_pKernel.getAssetManager();

    if( !m_pAssetManager )
        throw std::runtime_error( "Unable to get Omen Asset Manager!" );

  try {
    // Now load the plugins
    std::wcout << L"Loading plugins..." << std::endl;

    std::wstring pluginPath = m_pKernel.getProperty( L"plugin-path" );

    // Kernel loads all plugins from "plugins"-folder
    m_pKernel.loadPlugins( pluginPath );

    // Register this as an event handler
    m_pKernel.registerEventHandler(this);

    // Register event handlers
    registerEventFunc( this, &OmenGame::OnKeyboardEvent );
    registerEventFunc( this, &OmenGame::OnMouseEvent );
    registerEventFunc( this, &OmenGame::OnMouseWheelEvent );
    registerEventFunc( this, &OmenGame::OnFrameEvent );

    // Get the graphics server, which contains all graphics drivers got from loaded plugins
    Omen::IGraphicsServer* graphics = m_pKernel.getGraphicsServer();

    // List loaded drivers
    listGraphicsDrivers(graphics);

    // Get the first driver
    Omen::IGraphicsDriver& driver = graphics->getDriver(0);

    // Create renderer using the driver
    Omen::IRenderer* pRenderer = driver.createRenderer();
    m_pRenderer = pRenderer;
    //ShowCursor(FALSE);

    // Set message callback for the kernel
    //pRenderer->setMessageCallback(m_pKernel.GetWinProc());

    m_pSceneManager->setCurrentRenderer(pRenderer);

    // Get the input server, which contains all input drivers got from loaded plugins
    Omen::IInputServer* inputServer = m_pKernel.getInputServer();
    if( inputServer && inputServer->getDriverCount() != 0 )
    {
        Omen::IInputDriver& IInputDriver = inputServer->getDriver(0);
        unsigned long windowHandle = pRenderer->windowHandle();
        std::vector<Omen::IInputDevice*> devices =  IInputDriver.createInputDevices(windowHandle);
    }
  }
  catch(const std::exception &exception) {
     MessageBoxA( NULL, exception.what(), "Hwll", MB_OK );
    std::wcout << L"Unhandled error: " << exception.what() << std::endl;
    exit(-1);
  }

    // Initialize some dynamic properties
    Omen::Property pitch = 0.0;
    Omen::Property yaw = 0.0;
    Omen::Property roll = 0.0;
    Omen::Property player_speed = 0.0;

    m_pKernel.setProperty(L"rotation_pitch", pitch);
    m_pKernel.setProperty(L"rotation_yaw", yaw);
    m_pKernel.setProperty(L"rotation_roll",roll);
    m_pKernel.setProperty(L"player_speed_z",player_speed );

    // Get Data directory
    std::wstring data_dir = m_pKernel.getProperty( L"data-dir" );

    // Load assets
    unsigned long numTextures = m_pAssetManager->enumerateAssets( Omen::Asset_TextureMap, data_dir, true );
    unsigned long numModels   = m_pAssetManager->enumerateAssets( Omen::Asset_3dModel, data_dir, true );

    // Load models and add them to the scene manager
    // Get proper file reader for OmenModelFile-format
    Omen::IStorageServer* pStorageServer =  m_pKernel.getStorageServer();
    try {
        Omen::IModelFileReader* pModelFileReader = pStorageServer->getModelFileReader( data_dir.append( L"/test_scene.omf" ) );
        if( pModelFileReader )
        {
            if( pModelFileReader->loadFile() )
            {
                if( pModelFileReader->getSceneCount() != 0 )
                {
                    Omen::IScene** scenes = new Omen::IScene*[pModelFileReader->getSceneCount()];
                    pModelFileReader->getScenes( scenes, pModelFileReader->getSceneCount() );

                    // Add the scenes to the scene manager
                    for( unsigned long i=0; i < pModelFileReader->getSceneCount(); ++i )
                        m_pSceneManager->addScene(scenes[i]);

                    // Activate first scene
                    m_pSceneManager->setCurrentScene(scenes[0]);

                    const Omen::IScene* pCurrentScene = m_pSceneManager->currentScene();
                    if( pCurrentScene->cameraCount() != 0 )
                    {
                        Omen::ICamera** cameras = new Omen::ICamera*[pCurrentScene->cameraCount()];
                        pCurrentScene->getCameras( cameras, pCurrentScene->cameraCount() );
                        pCurrentScene->setCurrentCamera( cameras[0] );
                        if( !m_pCurrentCamera )
                            m_pCurrentCamera = cameras[0];
                        // Create octree for the scene
                        //const_cast<Omen::IScene*>(pCurrentScene)->createOctree( 3, 5 );
                    }
                    
                    if( pCurrentScene )
                    {
                        m_pTorusModel = pCurrentScene->findModel( L"Torus" );
                        //m_pCube = pCurrentScene->findModel( L"Cube" );
                        m_pUnitSphere = pCurrentScene->findModel( L"Sphere" );
                        if( m_pCurrentCamera && m_pUnitSphere )
                        {
                            Omen::SphereBoundingVolume* pSphere = dynamic_cast<Omen::SphereBoundingVolume*>(m_pCurrentCamera->boundingVolume(0.0));
                            if( pSphere )
                            {
                                m_pUnitSphere->setScale( Omen::Vector3d(pSphere->radius(), pSphere->radius(), pSphere->radius()) );
                            }
                        }
                    }
                }
            }
        }
    }catch( std::exception e )
    {
        MessageBoxA( NULL, e.what(), "Unable to load the game!", MB_OK );
    }

  return true;
}

/// <summary></summary>
/// <param name=""></param>
void OmenGame::run()
{

    // Start running the kernel
    m_pKernel.run();
}

void OmenGame::OnKeyboardEvent(const Omen::KeyboardEvent* event)
{
    //std::wcout << L"User Pressed key: " << (char)event->button() << std::endl;
    char* buttons = (char*)event->buttons();

    // Clear the button states
    m_keyUp = m_keyDown = m_keyLeft = m_keyRight = m_keyQ = m_keyE = false;

    if( buttons[32] )
    {
        bAnimate = !bAnimate;
        m_pCurrentCamera->setYaw( m_pCurrentCamera->yaw()+90.0f*Omen::PI_OVER_180 );
    }

    // Check out the movement control key states
    if( buttons['W'] )
    {
        m_keyUp = true;
        //std::wcout << "Up pressed!" << std::endl;
    }
    if( buttons['S'] )
    {
        m_keyDown = true;
        //std::wcout << "Down pressed!" << std::endl;
    }
    if( buttons['A'] )
    {
        m_keyLeft = true;
        //std::wcout << "Left pressed!" << std::endl;
    }
    if( buttons['D'] )
    {
        m_keyRight = true;
        //std::wcout << "Right pressed!" << std::endl;
    }
    if( buttons['Q'] )
    {
        m_keyQ = true;
    }
    if( buttons['E'] )
    {
        m_keyE = true;
    }
    if( buttons['P'] )
    {
        if( m_pCurrentCamera )
        {
            m_pCurrentCamera->setProperty( L"projection", L"perspective" );
        }
    }
    if( buttons['O'] )
    {
        if( m_pCurrentCamera )
        {
            m_pCurrentCamera->setProperty( L"projection", L"orthographic" );
        }
    }
    if( buttons['1'] )
    {
        
        Omen::ICamera** cameras = new Omen::ICamera*[m_pSceneManager->currentScene()->cameraCount()];
        m_pSceneManager->currentScene()->getCameras( cameras, m_pSceneManager->currentScene()->cameraCount() );
        //m_pSceneManager->currentScene()->setCurrentCamera( cameras[0] );
        m_pCurrentCamera = cameras[0];
        this->m_yaw = m_pCurrentCamera->yaw();
        this->m_pitch = m_pCurrentCamera->pitch();
    }
    if( buttons['2'] )
    {
        
        unsigned int cameraCount = m_pSceneManager->currentScene()->cameraCount();
        if( cameraCount > 1 )
        {
            Omen::ICamera** cameras = new Omen::ICamera*[m_pSceneManager->currentScene()->cameraCount()];
            m_pSceneManager->currentScene()->getCameras( cameras, m_pSceneManager->currentScene()->cameraCount() );
            m_pSceneManager->currentScene()->setCurrentCamera( cameras[1] );
            m_pCurrentCamera = cameras[1];
            this->m_yaw = m_pCurrentCamera->yaw();
            this->m_pitch = m_pCurrentCamera->pitch();
        }
    }
    if( buttons['3'] )
    {
        
        unsigned int cameraCount = m_pSceneManager->currentScene()->cameraCount();
        if( cameraCount > 2 )
        {
            Omen::ICamera** cameras = new Omen::ICamera*[m_pSceneManager->currentScene()->cameraCount()];
            m_pSceneManager->currentScene()->getCameras( cameras, m_pSceneManager->currentScene()->cameraCount() );
            m_pSceneManager->currentScene()->setCurrentCamera( cameras[2] );
            m_pCurrentCamera = cameras[2];
            this->m_yaw = m_pCurrentCamera->yaw();
            this->m_pitch = m_pCurrentCamera->pitch();
        }
    }
    if( buttons[187] )
    {
        int depth = m_pKernel.getProperty( L"octree_depth" );
        depth++;
        std::wstring strDepth;
        std::wstringstream ss(strDepth);
        ss << depth;
        m_pKernel.setProperty( L"octree_depth", ss.str() );
    }
    if( buttons[109] )
    {
        int depth = m_pKernel.getProperty( L"octree_depth" );
        depth--;
        if( depth < 0 )
            depth = 0;
        std::wstring strDepth;
        std::wstringstream ss(strDepth);
        ss << depth;
        m_pKernel.setProperty( L"octree_depth", ss.str() );
    }
        // Check if the ESC-button was pressed, which closes the application
    if( buttons[Omen::KeyboardEvent::Key_ESC] )
    {
        m_pKernel.shutDown();
    }
}

void OmenGame::getMovementDirection( Omen::Vector3d& direction )
{
    if( m_pCurrentCamera )
    {
        Omen::Float     rotationSpeed = m_pCurrentCamera->rotationSpeed();
        Omen::Vector3d  cameraVelocity = m_pCurrentCamera->velocity();

        if( m_keyUp )
        {
            //m_pCurrentCamera->setVelocity( Omen::Vector3d( cameraVelocity.x, cameraVelocity.y, 0.0f ) );
            direction.z = 1.0;
        }

        if( m_keyDown )
        {
            //m_pCurrentCamera->setVelocity( Omen::Vector3d( cameraVelocity.x, cameraVelocity.y, 0.0f ) );
            direction.z = -1.0;
        }

        if( m_keyLeft )
        {
            //m_pCurrentCamera->setVelocity( Omen::Vector3d( 0.0, cameraVelocity.y, cameraVelocity.z) );
            direction.x = -1.0;
        }

        if( m_keyRight )
        {
            //m_pCurrentCamera->setVelocity( Omen::Vector3d( 0.0, cameraVelocity.y, cameraVelocity.z) );
            direction.x = 1.0;
        }

        if( m_keyQ )
        {
            direction.y = 1.0;
        }

        if( m_keyE )
        {
            direction.y = -1.0;
        }
    }
}

void OmenGame::updateCamera()
{
    if( m_pCurrentCamera )
    {
        Vector3d movementDirection;
        getMovementDirection(movementDirection);
        static Omen::Float previousTime = m_pKernel.timer() ? m_pKernel.timer()->totalTime() : 0.0f;
        Omen::Float currentTime = m_pKernel.timer() ? m_pKernel.timer()->totalTime() : 0.0f;
        m_pCurrentCamera->updatePosition( movementDirection, currentTime-previousTime );
        previousTime = m_pKernel.timer() ? m_pKernel.timer()->totalTime() : 0.0f;
    }
}

void OmenGame::OnMouseEvent(const Omen::MouseEvent* event)
{
    static int mouseX = event->mousePosition().x;
    static int mouseY = event->mousePosition().y;
    
    int newMouseX = event->mousePosition().x;
    int newMouseY = event->mousePosition().y;
    
    if( m_pCurrentCamera && mouseX != newMouseX && mouseY != newMouseY )
    {
        int mouseXD = newMouseX - mouseX;
        int mouseYD = newMouseY - mouseY;

        if( true /*event->buttonStates() == Omen::MouseEvent::Button_Middle*/ ) 
        {
            m_yaw = m_pCurrentCamera->yaw();
            m_pitch = m_pCurrentCamera->pitch();
            m_pitch = static_cast<Omen::Float>(mouseYD) * m_pCurrentCamera->rotationSpeed()*this->m_pKernel.timer()->elapsedTime();
            m_yaw   = static_cast<Omen::Float>(mouseXD) * m_pCurrentCamera->rotationSpeed()*this->m_pKernel.timer()->elapsedTime();

            m_pCurrentCamera->setYaw( m_yaw );
            m_pCurrentCamera->setPitch( m_pitch );

            if( m_pRenderer )
            {
                HWND windowHandle = (HWND)m_pRenderer->windowHandle();
                RECT wndRect;
                GetWindowRect(windowHandle, &wndRect);
                //SetCursorPos( wndRect.left+(wndRect.right-wndRect.left)/2, wndRect.top+(wndRect.bottom-wndRect.top)/2 );

            }
        }
    }

    mouseX = event->mousePosition().x;
    mouseY = event->mousePosition().y;
    //std::wcout << L"Mouse coordinates: " << mouseX << L", " << mouseY << std::endl;
}

void OmenGame::OnFrameEvent(const Omen::FrameEvent* event )
{
    // Clear the debug log
    //std::system ( "CLS" );

    if( m_pCurrentCamera )
    {
        updateCamera();
        m_pCurrentCamera->updateCameraMatrix();
    }
    if( m_pTorusModel )
    {
        static Omen::Float angle = 0.0;
        angle += 0.1f;
        m_pTorusModel->getMesh(0)->setRotation(0.0,angle, 0.0);
        Omen::Vector3d pos = m_pTorusModel->getMesh(0)->globalPosition();
        m_pTorusModel->getMesh(0)->setPosition( Omen::Vector3d(-1.0f+1.0f*sin(angle),m_pTorusModel->getMesh(0)->globalPosition().y,m_pTorusModel->getMesh(0)->globalPosition().z) );
    }

    if( m_pCube )
    {
        static Omen::Float angle = 0.0;
        angle += 0.01f;
        m_pCube->getMesh(0)->setRotation(0.0f,0.0f, angle);
        Omen::Vector3d pos = m_pCube->getMesh(0)->globalPosition();
        m_pCube->getMesh(0)->setPosition( Omen::Vector3d(-10.0f,2.0f,-5.0f+5*sin(angle)) );
    }

    if( m_pUnitSphere )
    {
        unsigned long cameraCount = m_pSceneManager->currentScene()->cameraCount();
        if( cameraCount > 1 )
        {
            Omen::ICamera** cameras = new Omen::ICamera*[cameraCount];
            m_pSceneManager->currentScene()->getCameras( cameras, m_pSceneManager->currentScene()->cameraCount() );
            //m_pUnitSphere->getMesh(0)->setPosition( cameras[0]->position() );
        }
    }
}

void OmenGame::OnMouseWheelEvent(const Omen::MouseWheelEvent* event)
{
    int mouseZ = event->wheelDelta();
    
    if( m_pCurrentCamera )
    {
        
    }
}


/// <summary>Example method that lists available graphics drivers</summary>
/// <param name="graphicsServer">
///   Storage server that will be used to open the zip archive
/// </param>
void listGraphicsDrivers( Omen::IGraphicsServer* graphicsServer) {
  for(size_t index = 0; index < graphicsServer->getDriverCount(); ++index) {
    std::wcout << L"\t" << graphicsServer->getDriver(index).getName() << std::endl;
  }

  if(graphicsServer->getDriverCount() == 0) {
    std::wcout << L"\t" << L"None" << std::endl;
  }

  std::wcout << std::endl;
}
